#pragma once

#include <vex/core/config.h>
#include <vex/iterable/stl_interop/range_from_iterator_adapter_body.h>
#include <vex/iterable/stl_interop/convert_traits.h>
#include <vex/iterable/adapter/adapter_default.h>
#include <vex/iterable/adapter/adapter_access.h>
#include <type_traits>

namespace vex { namespace iterable { 
    
    namespace stl_interop {
        // some sfinae helpers first...

        template< class TItr, class TItrTraits = typename std::iterator_traits<TItr> > 
        class range_from_iterator;

        /// many stl output iterators define strange values for their internal types,
        /// this meta function unifies output iterator traits for vex usage
        template<class TRef>
        struct output_iterator_traits {
            typedef std::output_iterator_tag iterator_category;
            typedef TRef reference;
            typedef typename std::remove_reference<reference>::type value_type;
            typedef typename std::add_pointer<value_type>::type pointer;
        };

        /// <summary>
        /// Computes the type of <c>range_from_iterator<c/> based on <paramref name="TItr"/> and
        /// <paramref name="TItrTraits"/>. Used mainly in lazy_enable_if expressions.
        /// </summary>
        template <class TItr, class TItrTraits = std::iterator_traits<typename std::decay<TItr>::type> >
        struct make_range_from_iterator {
            typedef typename std::decay<TItr>::type iterator;
            typedef range_from_iterator<iterator, TItrTraits> type;
        };

        /// <summary>
        /// Computes the type of <c>range_from_iterator</c> based on <paramref name="TContainer"/>. 
        /// Used mainly in lazy_enable_if expressions.
        /// </summary>
        template <class TContainer> class make_range_from_container {
            typedef typename std::remove_const<TContainer>::type container_t;
            typedef TContainer const_container_t;
            typedef typename std::conditional<std::is_same<container_t, const_container_t>::value,
                typename container_t::iterator,
                typename container_t::const_iterator
            >::type iterator_type;
        public:
            typedef range_from_iterator<iterator_type> type;
            //typedef typename std::decay<decltype(std::begin(std::declval<TContainer&>()))>::type beg_iterator_t;
            //typedef typename std::decay<decltype(std::end(std::declval<TContainer&>()))>::type end_iterator_t;
            //
            //static_assert(std::is_same<beg_iterator_t, beg_iterator_t>::value,
            //    "begin and end iterators must be of the same type!");

            //typedef range_from_iterator<beg_iterator_t> type;
        };

        /// <summary>
        /// Range which wraps a range of stl iterators or a stl output iterator. The category is infered
        /// automatically based on analysis of <paramref name="TItrTraits"/>
        /// </summary>
        template< class TItr, class TItrTraits > 
        class range_from_iterator : public range_base<
            typename convert_iterator_traits<TItr, TItrTraits>::category,
            typename convert_iterator_traits<TItr, TItrTraits>::reference,
            adapter::adapter_default< // this inherits a proper subset of range member functions
                range_from_iterator<TItr, TItrTraits>, 
                typename convert_iterator_traits<TItr, TItrTraits>::category, 
                typename convert_iterator_traits<TItr, TItrTraits>::reference
            >
        > 
        {
            // grant access to other instantiations for conversions
            template <class, class> friend class range_from_iterator;

            // define nested adaptee_type for adapter_access
            typedef range_from_iterator_adapter_body<
                TItr,
                typename convert_iterator_traits<TItr, TItrTraits>::category, 
                typename convert_iterator_traits<TItr, TItrTraits>::reference
            > adaptee_type;

            // grant access to adapter
            friend class adapter::adapter_access;

            /// copy construct from adaptee body, required by adapter_access and adapter_default protocols
            range_from_iterator(adaptee_type const & p_adapter_body)
                : m_adapter_body(p_adapter_body) {}

            /// provide access to adapter body
            adaptee_type& get_adaptee_ref() { return m_adapter_body; }
            adaptee_type const& get_adaptee_ref() const { return m_adapter_body; }

            adaptee_type m_adapter_body;
        public:
            /// construct from iterator range
            range_from_iterator(TItr p_beg, TItr p_end)
                : m_adapter_body(p_beg, p_end) {}

            /// construct from single iterator, which must be of output iterator category
            range_from_iterator(TItr p_beg, std::output_iterator_tag const & p_tag)
                : m_adapter_body(p_beg, p_tag) {}

            /// conversion from compatible types
            template<class UItr, class UItrTraits> range_from_iterator(
                range_from_iterator<UItr, UItrTraits> const & p_other, 
                typename std::enable_if<std::is_convertible<UItr, TItr>::value>::type* = 0)
                : m_adapter_body(p_other.m_adapter_body) {}

            range_from_iterator(range_from_iterator && p_other) 
                : m_adapter_body(std::move(p_other.m_adapter_body)) {}

            range_from_iterator& operator = (range_from_iterator && p_other) 
            {
                range_from_iterator(std::move(p_other)).swap(*this);
                return *this;
            }

            void swap(range_from_iterator& p_other)
            {
                p_other.m_adapter_body.swap(this->m_adapter_body);
            }
        };
    }

    /// <summary>
    /// Create a vex range from a pair of stl iterators. The type of resulting range is 
    /// infered automatically from iterators.
    /// </summary>
    ///
    /// <param name="p_beg">
    /// Iterator which determines the beginning of the range
    /// </param>
    /// <param name="p_end">
    /// Iterator which determines the end of the range
    /// </param>
    template<class TItr> auto make_range(TItr p_beg, TItr p_end)
        -> stl_interop::range_from_iterator<typename std::remove_reference<TItr>::type>
    {
        return stl_interop::range_from_iterator<typename std::remove_reference<TItr>::type>(
            p_beg, p_end);
    }

    /// <summary>
    /// Create a vex range from a compile time C array. The type of resulting range is 
    /// infered automatically from the type of the array. The resulting range will be "writable"
    /// </summary>
    ///
    /// <param name="p_container">
    /// A l-value reference to a compile time C array
    /// </param>
    template<class T, std::size_t Size> auto make_range(T (&p_container)[Size])
        -> stl_interop::range_from_iterator<T*>
    {
        return stl_interop::range_from_iterator<T*>(
            std::begin(p_container),
            std::end(p_container));
    }

    /// <summary>
    /// Create a vex range from a compile time C array. The type of resulting range is 
    /// infered automatically from the type of the array. The resulting range will be only "readable"
    /// </summary>
    ///
    /// <param name="p_container">
    /// A reference to a compile time C array.
    /// </param>
    template<class T, std::size_t Size> auto make_range(const T (&p_container)[Size])
        -> stl_interop::range_from_iterator<T const*>
    {
        return stl_interop::range_from_iterator<T const*>(
            std::begin(p_container),
            std::end(p_container));
    }

    /// <summary>
    /// Create a vex range from a type which satisfies the concept of stl container or boost range.
    /// The type of the resulting range is infered automatically from the type of <paramref name="p_container"/> . 
    /// The resulting range will be only "writable", if the container is "writable".
    /// </summary>
    ///
    /// <param name="p_container">
    /// A l-value reference to a container-like type.
    /// </param>
    template<class TContainer> auto make_range(TContainer& p_container)
        -> stl_interop::range_from_iterator<typename TContainer::iterator>
    {
        return stl_interop::range_from_iterator<typename TContainer::iterator>(
            p_container.begin(), p_container.end());
    }

    /// <summary>
    /// Create a vex range from a type which satisfies the concept of stl container or boost range.
    /// The type of the resulting range is infered automatically from the type of <paramref name="p_container"/> . 
    /// This resulting range can be safely used only with "stateless" ranges such as ranges created by
    /// Boost.Range library. The state and data of the range must be completely representable by its iterators.
    /// </summary>
    ///
    /// <param name="p_container">
    /// An instance (r-lvalue or or l-value) of a container-like type.
    /// </param>
    template<class TContainer> auto make_range(TContainer const& p_container)
        -> stl_interop::range_from_iterator<typename TContainer::const_iterator>
    {
        return stl_interop::range_from_iterator<typename TContainer::const_iterator>(
            p_container.begin(), p_container.end());
    }

    /// <summary>
    /// Make output range from stl output iterator. You need to explicitely specify the
    /// reference type accepted by the output range.
    /// </summary>
    ///
    /// <param name="p_beg">
    /// Instance of a type which represents the output iterator
    /// </param>
    template<class TRef, class TOutItr> auto make_output_range(TOutItr p_beg)
        -> stl_interop::range_from_iterator<TOutItr, stl_interop::output_iterator_traits<TRef> >
    {
        return stl_interop::range_from_iterator<TOutItr, stl_interop::output_iterator_traits<TRef> >(
            p_beg, 
            std::output_iterator_tag());
    }
}}